Welcome to FlagSet!
FlagSet is a new, ergonomic approach to handling flags that combines the
best of existing crates like bitflags
and enumflags
without their
downsides.
Existing Implementations
The bitflags
crate has long been part of the Rust ecosystem.
Unfortunately, it doesn't feel like natural Rust. The bitflags
crate
uses a wierd struct format to define flags. Flags themselves are just
integers constants, so there is little type-safety involved. But it doesn't
have any dependencies. It also allows you to define implied flags (otherwise
known as overlapping flags).
The enumflags
crate tried to improve on bitflags
by using enumerations
to define flags. This was a big improvement to the natural feel of the code.
Unfortunately, there are some design flaws. To generate the flags,
procedural macros were used. This implied two separate crates plus
additional dependencies. Further, enumflags
specifies the size of the
flags using a repr($size)
attribute. Unfortunately, this attribute
cannot resolve type aliases, such as c_int
. This makes enumflags
a
poor fit for FFI, which is the most important place for a flags library.
The enumflags
crate also disallows overlapping flags and is not
maintained.
FlagSet improves on both of these by adopting the enumflags
natural feel
and the bitflags
mode of flag generation; as well as additional API usage
niceties. FlagSet has no dependencies and is extensively documented and
tested. It also tries very hard to prevent you from making mistakes by
avoiding external usage of the integer types. FlagSet is also a zero-cost
abstraction: all functions are inlineable and should reduce to the core
integer operations. FlagSet also does not depend on stdlib, so it can be
used in no_std
libraries and applications.
Defining Flags
Flags are defined using the flags!
macro:
use ;
use c_int;
flags!
Notice that a flag definition looks just like a regular enumeration, with the addition of the field-size type. The field-size type is required and can be either a type or a type alias. Both examples are given above.
Also note that the field-size type specifies the size of the corresponding
FlagSet
type, not size of the enumeration itself. To specify the size of
the enumeration, use the repr($size)
attribute as specified below.
Flag Values
Flags often need values assigned to them. This can be done implicitly, where the value depends on the order of the flags:
use ;
flags!
Alternatively, flag values can be defined explicitly, by specifying any
const
expression:
use ;
flags!
Flags can also overlap or "imply" other flags:
use ;
flags!
Specifying Attributes
Attributes can be used on the enumeration itself or any of the values:
use ;
flags!
Collections of Flags
A collection of flags is a FlagSet<T>
. If you are storing the flags in
memory, the raw FlagSet<T>
type should be used. However, if you want to
receive flags as an input to a function, you should use
impl Into<FlagSet<T>>
. This allows for very ergonomic APIs:
use ;
flags!
;
assert_eq!;
assert_eq!;
assert_eq!;
Operations
Operations can be performed on a FlagSet<F>
or on individual flags:
Operator | Assignment Operator | Meaning |
---|---|---|
| | |= | Union |
& | &= | Intersection |
^ | ^= | Toggle specified flags |
- | -= | Difference |
% | %= | Symmetric difference |
! | Toggle all flags |
Optional Serde support
Serde support can be enabled with the 'serde' feature flag. You can then serialize and
deserialize FlagSet<T>
to and from any of the supported formats:
use ;
flags!
let flagset = Foo | Bar;
let json = to_string.unwrap;
let flagset: = from_str.unwrap;
assert_eq!;
For serialization and deserialization of flags enum itself, you can use the serde_repr
crate
(or implement serde::ser::Serialize
and serde:de::Deserialize
manually), combined with the
appropriate repr
attribute:
use ;
use ;
flags!
let json = to_string.unwrap;
let flag: Flags = from_str.unwrap;
assert_eq!;